﻿/// <reference path="../../lib/underscore.js" />
/// <reference path="//Microsoft.WinJS.1.0/js/base.js" />

var PortfolioCalculator = WinJS.Class.define(
    function (portfolio, simple) {
        this.portfolio = portfolio;
        this.simple = simple;
        this.refresh();
        this.weightedIds = 0;
    },
    {
        _processSolution: function(solution, frontier, onComplete) {
            var that = this;

            if (!frontier) frontier = [];

            if (!solution) solution = { ExpectedReturn: Number.MIN_VALUE, isNew: true };

            if (solution.Optimal || solution.isNew) {
                var expReturn;
                if (!solution.isNew) {
                    frontier.push(solution);
                    expReturn = solution.ExpectedReturn + 0.0025;
                } else {
                    expReturn = 0;
                }
                that.optimizeAllocations(expReturn).then(function (solution) { that._processSolution(solution, frontier, onComplete); })
            } else {
                onComplete(frontier);
            }
        },
        _getCAPMOptimizationInfo: function(minimumReturn, step, riskFreeRate, marketRiskPremium) {
            var that = this;

            var requests = [];

            that.portfolio.assets.forEach(function (symbol) {
                var aCalc;
                var stockObject = {
                    Symbol: symbol
                };

                var request = that.getAssetCalculator(symbol).then(function (calc) {
                    aCalc = calc;
                    return aCalc.getCAPMAnnualMeanReturnRate(riskFreeRate, marketRiskPremium);
                }).then(function (rate) {
                    stockObject.MeanReturnRate = rate;
                    return aCalc.getPortfolioCovarianceValues();
                }).then(function (covVector) {
                    var covVectorCopy = {};
                    for (var key in covVector) {
                        if (key != "SPY") {
                            covVectorCopy[key] = covVector[key];
                        }
                    }
                    stockObject.Covariances = covVectorCopy;
                    return stockObject;
                });

                requests.push(request);
            });

            return WinJS.Promise
                .thenEach(requests)
                .then(function (stocks) {
                    var requestObj = {
                        MinimumReturn: minimumReturn,
                        Step: step,
                        Stocks: stocks
                    };

                    return requestObj;
                });
        },
        _getOptimizationInfo: function (minimumReturn, step) {
            var that = this;

            var requests = [];

            that.portfolio.assets.forEach(function (symbol) {
                var aCalc;
                var stockObject = {
                    Symbol: symbol
                };

                var request = that.getAssetCalculator(symbol).then(function (calc) {
                    aCalc = calc;
                    return aCalc.getAnnualizedMeanRate();
                }).then(function (rate) {
                    stockObject.MeanReturnRate = rate;
                    return aCalc.getPortfolioCovarianceValues();
                }).then(function (covVector) {
                    var covVectorCopy = {};
                    for (var key in covVector) {
                        if (key != "SPY") {
                            covVectorCopy[key] = covVector[key];
                        }
                    }
                    stockObject.Covariances = covVectorCopy;
                    return stockObject;
                });

                requests.push(request);
            });

            return WinJS.Promise
                .thenEach(requests)
                .then(function (stocks) {
                    var requestObj = {
                        MinimumReturn: minimumReturn,
                        Step: step,
                        Stocks: stocks
                    };

                    return requestObj;
                });
        },
        _fromOptimizationResult: function (result) {
            var that = this;

            var retObject = {
                Feasible: result.Feasible,
                Optimal: result.Optimal,
                ExpectedReturn: result.ExpectedReturn,
                Allocations: {}
            };

            for (var index in result.Results) {
                var stock = result.Results[index];
                retObject.Allocations[stock.Symbol] = stock.Allocation;
            }

            return that.getAnnualizedStandardDeviation(retObject.Allocations).then(
                function (StdDev) {
                    retObject.StandardDeviation = StdDev;
                    return retObject;
                }
            );
        },
        _fromOptimizationList: function (list) {
            var that = this;

            var outputListRequest = [];
            for (var index in list) {
                outputListRequest.push(that._fromOptimizationResult(list[index]));
            }

            return WinJS.Promise.thenEach(outputListRequest);
        },
        getEfficientFrontier: function (minimumReturn, step) {
            var that = this;

            return that._getOptimizationInfo(minimumReturn, step).then(
                function (info) {
                    return WinJS.xhr({

                        type: "POST",
                        //url: "http://localhost:37201/api/frontier",
                        url: "http://optimization.andrewgaspar.com/api/frontier",
                        responseType: "json",
                        data: JSON.stringify(info),
                        headers: {
                            "Content-Type": "application/json"
                        }
                    });
                }
            ).then(
                function(response) {
                    var object = JSON.parse(response.responseText);
                    return that._fromOptimizationList(object);
                }
            ).then(function (list) {
                var minimum = _.min(list, function (opt) {
                    return opt.StandardDeviation;
                });

                list = _.filter(list, function (opt) {
                    return opt.ExpectedReturn >= minimum.ExpectedReturn;
                });

                return list;
            });
        },
        optimizeCAPMAllocations: function(minimumReturn, riskFreeRate, marketRiskPremium) {
            var that = this;

            return that._getCAPMOptimizationInfo(minimumReturn,0,riskFreeRate,marketRiskPremium).then(
                function (requestObj) {

                    return WinJS.xhr({

                        type: "POST",
                        //url: "http://localhost:37201/api/optimize",
                        url: "http://optimization.andrewgaspar.com/api/optimize",
                        responseType: "json",
                        data: JSON.stringify(requestObj),
                        headers: {
                            "Content-Type": "application/json"
                        }
                    });
                }).then(function (response) {
                    var object = JSON.parse(response.responseText);

                    return that._fromOptimizationResult(object);
                });
        },
        optimizeAllocations: function (minimumReturn) {
            var that = this;

            return that._getOptimizationInfo(minimumReturn).then(
                function(requestObj) {

                    return WinJS.xhr({

                        type: "POST",
                        //url: "http://localhost:37201/api/optimize",
                        url: "http://optimization.andrewgaspar.com/api/optimize",
                        responseType: "json",
                        data: JSON.stringify(requestObj),
                        headers: {
                            "Content-Type": "application/json"
                        }
                    });
                }).then(function (response) {
                    var object = JSON.parse(response.responseText);

                    return that._fromOptimizationResult(object);
                });
        },
        getCAPMSharpe: function (weights, riskFreeRate, mRP) {
            var that = this;

            that._verifyWeights(weights);

            var irxCalc;
            var myAnnualizedRate;
            var myAnnualizedDeviation;

            return that.getIRX().then(
                    function (calc) {
                        irxCalc = calc;
                        return that.getCAPMAnnualizedMeanRate(weights, riskFreeRate, mRP);
                    }
                ).then(
                    function (rate) {
                        myAnnualizedRate = rate;
                        return that.getAnnualizedStandardDeviation(weights);
                    }
                ).then(
                    function (stdDev) {
                        myAnnualizedDeviation = stdDev;

                        var irxAnnualizedRate = _.reduce(irxCalc.asset.prices, function (memo, rate) {
                            return memo + rate.adjustedClosePrice;
                        }, 0);

                        irxAnnualizedRate = irxAnnualizedRate / irxCalc.asset.prices.length / 100 / 360 * 252;

                        var sharpeRatio = (myAnnualizedRate - irxAnnualizedRate) / myAnnualizedDeviation;
                        return sharpeRatio;
                    }
                );
        },
        getCAPMAnnualizedMeanRate: function (weights, riskFreeRate, mRP) {
            var that = this;

            that._verifyWeights(weights);

            var dailyMeanRates = {};
            var dailyRate = 0;

            return that.getAssetCalculators().then(
                    function (calcs) {
                        var dailyRateRequests = [];

                        calcs.forEach(function (calc) {
                            var request = calc.getCAPMAnnualMeanReturnRate(riskFreeRate, mRP).then(function (rate) {
                                return rate * weights[calc.asset.symbol];
                            });

                            dailyRateRequests.push(request);
                        });

                        return WinJS.Promise.thenEach(dailyRateRequests);
                    }
                ).then(function (weightedRates) {
                    return _.reduce(weightedRates, function (memo, num) { return memo + num; }, 0);
                });
        },
        getSharpe: function (weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].sharpeRequest) {

                var irxCalc;
                var myAnnualizedRate;
                var myAnnualizedDeviation;

                that.requests.weightedRequests[weights.id].sharpeRequest =
                    that.getIRX().then(
                        function (calc) {
                            irxCalc = calc;
                            return that.getAnnualizedMeanRate(weights);
                        }
                    ).then(
                        function (rate) {
                            myAnnualizedRate = rate;
                            return that.getAnnualizedStandardDeviation(weights);
                        }
                    ).then(
                        function (stdDev) {
                            myAnnualizedDeviation = stdDev;

                            var irxAnnualizedRate = _.reduce(irxCalc.asset.prices, function (memo, rate) {
                                return memo + rate.adjustedClosePrice;
                            }, 0);

                            irxAnnualizedRate = irxAnnualizedRate / irxCalc.asset.prices.length / 100 / 360 * 252;

                            var sharpeRatio = (myAnnualizedRate - irxAnnualizedRate) / myAnnualizedDeviation;
                            return sharpeRatio;
                        }
                    );
            }

            return that.requests.weightedRequests[weights.id].sharpeRequest;
        },
        getBeta: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].betaRequest) {

                var marketCovariance;

                that.requests.weightedRequests[weights.id].betaRequest =
                    that.getMarketCovariance(weights).then(
                        function (cov) {
                            marketCovariance = cov;
                            return that.getSPY();
                        }
                    ).then(function (spyCalc) {
                        return spyCalc.getDailyVariance();
                    }).then(function (marketVariance) {
                        return marketCovariance / marketVariance;
                    });
            }

            return that.requests.weightedRequests[weights.id].betaRequest;
        },
        getMarketCorrelation: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].marketCorrelationRequest) {

                var marketCovariance;
                var stdDev;

                that.requests.weightedRequests[weights.id].marketCorrelationRequest =
                    that.getMarketCovariance(weights).then(function (mCov) {
                        marketCovariance = mCov;
                        return that.getDailyStandardDeviation(weights);
                    }).then(function (s) {
                        stdDev = s;
                        return that.getSPY();
                    }).then(function (spyCalc) {
                        return spyCalc.getDailyStandardDeviationRiskOnlyRates();
                    }).then(function (spyStdDev) {
                        return marketCovariance / (stdDev * spyStdDev);
                    });
            }

            return that.requests.weightedRequests[weights.id].marketCorrelationRequest;
        },
        getMarketCovariance: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].marketCovarianceRequest) {
                that.requests.weightedRequests[weights.id].marketCovarianceRequest =
                    that.getCovarianceMatrix().then(
                        function (matrix) {
                            var covariance = 0;

                            that.portfolio.assets.forEach(function (sym) {
                                if (sym != "id") {
                                    covariance += weights[sym] * matrix[sym]["SPY"];
                                }
                            });

                            return covariance;
                        }
                    );
            }

            return that.requests.weightedRequests[weights.id].marketCovarianceRequest;
        },
        getAnnualizedStandardDeviation: function(weights) {
            var that = this;

            that._verifyWeights(weights);
            if (!that.requests.weightedRequests[weights.id].annualizedStandardDeviationRequest) {
                that.requests.weightedRequests[weights.id].annualizedStandardDeviationRequest =
                    that.getAnnualizedVariance(weights).then(function (variance) {
                        return Math.sqrt(variance);
                    });
            }

            return that.requests.weightedRequests[weights.id].annualizedStandardDeviationRequest;
        },
        getDailyStandardDeviation: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].dailyStandardDeviationRequest) {
                that.requests.weightedRequests[weights.id].dailyStandardDeviationRequest =
                    that.getDailyVariance(weights).then(function (variance) {
                        return Math.sqrt(variance);
                    });
            }

            return that.requests.weightedRequests[weights.id].dailyStandardDeviationRequest;
        },
        getAnnualizedVariance: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].annualizedVarianceRequest) {

                that.requests.weightedRequests[weights.id].annualizedVarianceRequest =
                    that.getDailyVariance(weights).then(function (variance) {
                        return variance * 252;
                    });
            }

            return that.requests.weightedRequests[weights.id].annualizedVarianceRequest;
        },
        getDailyVariance: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].dailyVarianceRequest) {

                that.requests.weightedRequests[weights.id].dailyVarianceRequest =
                    that.getCovarianceMatrix().then(
                        function (matrix) {
                            var variance = 0;

                            that.portfolio.assets.forEach(function (sym1) {
                                that.portfolio.assets.forEach(function (sym2) {
                                    variance += weights[sym1] * weights[sym2] * matrix[sym1][sym2];
                                });
                            });

                            return variance;
                        }
                    );
            }

            return that.requests.weightedRequests[weights.id].dailyVarianceRequest;
        },
        getAnnualizedMeanRate: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].annualizedMeanRateRequest) {
                that.requests.weightedRequests[weights.id].annualizedMeanRateRequest =
                    that.getDailyMeanRate(weights).then(
                        function (rate) {
                            return WinJS.Promise.wrap(rate * 252);
                        }
                    );
            }

            return that.requests.weightedRequests[weights.id].annualizedMeanRateRequest;
        },
        getDailyMeanRate: function(weights) {
            var that = this;

            that._verifyWeights(weights);

            if (!that.requests.weightedRequests[weights.id].dailyMeanRateRequest) {

                var dailyMeanRates = {};
                var dailyRate = 0;

                that.requests.weightedRequests[weights.id].dailyMeanRateRequest =
                    that.getAssetCalculators().then(
                        function (calcs) {
                            var dailyRateRequests = [];

                            calcs.forEach(function (calc) {
                                var request = calc.getDailyMeanRate().then(function (rate) {
                                    return WinJS.Promise.wrap(rate * weights[calc.asset.symbol]);
                                });

                                dailyRateRequests.push(request);
                            });

                            return WinJS.Promise.thenEach(dailyRateRequests);
                        }
                    ).then(function (weightedRates) {
                        return _.reduce(weightedRates, function (memo, num) { return memo + num; }, 0);
                    });
            }

            return that.requests.weightedRequests[weights.id].dailyMeanRateRequest;
        },
        getCorrelationMatrix: function() {
            var that = this;

            if (!that.requests._getCorrelationMatrixRequest) {
                var assetCalcs;
                var correlationMatrix = {};

                that.requests._getCorrelationMatrixRequest =
                    that.getAssetCalculatorsPlusSPY().then(
                        function (calcs) {
                            assetCalcs = calcs;

                            var correlationRequests = [];

                            assetCalcs.forEach(function (calc) {
                                var request = calc.getPortfolioCorrelationValues().then(
                                    function (corVec) {
                                        return WinJS.Promise.wrap(correlationMatrix[calc.asset.symbol] = corVec);
                                    }
                                );

                                correlationRequests.push(request);
                            });

                            return WinJS.Promise.thenEach(correlationRequests);
                        }
                    ).then(
                        function () {
                            return WinJS.Promise.wrap(correlationMatrix);
                        }
                    );
            }

            return that.requests._getCorrelationMatrixRequest;
        },
        getCovarianceMatrix: function () {
            var that = this;

            if (!that.requests._getCovarianceMatrixRequest) {
                that.requests._getCovarianceMatrixRequest = new WinJS.Promise(
                    function (complete) {
                        var assetCalcs;

                        that.getAssetCalculatorsPlusSPY().then(
                            function (calcs) {
                                assetCalcs = calcs;

                                var covarianceRequests = [];
                                var covarianceMatrix = {};

                                assetCalcs.forEach(function (calc) {
                                    var request = calc.getPortfolioCovarianceValues().then(
                                        function (cov) {
                                            return WinJS.Promise.wrap(covarianceMatrix[calc.asset.symbol] = cov);
                                        }
                                    );

                                    covarianceRequests.push(request);
                                });

                                WinJS.Promise.thenEach(assetCalcs).done(
                                    function () {
                                        complete(covarianceMatrix);
                                    }
                                );
                            }
                        );
                    }
                );
            }

            return that.requests._getCovarianceMatrixRequest;
        },
        getGSPC: function() {
            var that = this;
            return that.getAssetCalculator("^GSPC");
        },
        getIRX: function() {
            var that = this;
            return that.getAssetCalculator("^IRX");
        },
        getSPY: function() {
            var that = this;
            return that.getAssetCalculator("SPY");
        },
        getNumberOfAssets: function () {
            var that = this;
            return WinJS.Promise.wrap(that.portfolio.assets.length);
        },
        getTotalAnnualizedStandardDeviationWeights: function() {
            var that = this;

            if (!that.requests._totalAnnualizedStandardDeviationWeights) {
                var weightObj = {};

                that.requests._totalAnnualizedStandardDeviationWeights = that.getAssetCalculators().then(
                    function (calcs) {
                        

                        var requests = [];

                        calcs.forEach(function (calc) {
                            var req = calc.getAnnualizedStandardDeviationWeight().then(
                                function (weight) {
                                    weightObj[calc.asset.symbol] = weight;
                                }
                            );
                            
                            requests.push(req);
                        });

                        return WinJS.Promise.thenEach(requests);
                    }
                ).then(
                function(){
                    return weightObj;
                });
            }

            return that.requests._totalAnnualizedStandardDeviationWeights;
        },
        getTotalAnnualizedStandardDeviationWeight: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that.requests._totalAnnualizedStandardDeviationWeight) {
                        that.getAssetCalculators().then(
                            function (calcs) {

                                var pDeviations = [];
                                var total = 0;
                                calcs.forEach(
                                    function (calc) {
                                        calc.getAnnualizedStandardDeviationWeight().then(
                                            function (dev) {
                                                total += dev;
                                            }
                                        );
                                    }
                                );

                                WinJS.Promise.join(pDeviations).done(
                                    function () {
                                        that.requests._totalAnnualizedStandardDeviationWeight = total;
                                        complete(that.requests._totalAnnualizedStandardDeviationWeight);
                                    }
                                );
                            }
                        );
                    } else complete(that.requests._totalAnnualizedStandardDeviationWeight);
                }
            );
        },
        getTotalAnnualizedStandardDeviationFactor: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that.requests._totalAnnualizedStandardDeviationFactor) {
                        that.getAssetCalculators().then(
                            function (calcs) {

                                var pDeviations = [];
                                var total = 0;
                                calcs.forEach(
                                    function (calc) {
                                        calc.getAnnualizedStandardDeviationFactor().then(
                                            function (dev) {
                                                total += dev;
                                            }
                                        );
                                    }
                                );

                                WinJS.Promise.join(pDeviations).done(
                                    function () {
                                        that.requests._totalAnnualizedStandardDeviationFactor = total;
                                        complete(that.requests._totalAnnualizedStandardDeviationFactor);
                                    }
                                );
                            }
                        );
                    } else complete(that.requests._totalAnnualizedStandardDeviationFactor);
                }
            );
        },
        getTotalAnnualizedStandardDeviation: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that.requests._totalAnnualizedStandardDeviation) {
                        that.getAssetCalculators().then(
                            function (calcs) {

                                var pDeviations = [];
                                var total = 0;
                                calcs.forEach(
                                    function (calc) {
                                        calc.getAnnualizedStandardDeviation().done(
                                            function (dev) {
                                                total += dev;
                                            }
                                        );
                                    }
                                );

                                WinJS.Promise.join(pDeviations).done(
                                    function () {
                                        that.requests._totalAnnualizedStandardDeviation = total;
                                        complete(that.requests._totalAnnualizedStandardDeviation);
                                    }
                                );
                            }
                        );
                    } else complete(that.requests._totalAnnualizedStandardDeviation);
                }
            );
        },
        getAssetCalculatorsPlusSPY: function() {
            var that = this;

            if (!that.requests._assetCalculatorsPlusSPY) {
                that.requests._assetCalculatorsPlusSPY = new WinJS.Promise(
                    function (complete) {
                        var assetCalcs;

                        that.getAssetCalculators().then(
                            function (calcs) {
                                assetCalcs = calcs;
                                return that.getSPY();
                            }
                        ).done(
                            function (spy) {
                                assetCalcs.push(spy);
                                complete(assetCalcs);
                            }
                        );
                    }
                );
            }

            return that.requests._assetCalculatorsPlusSPY;
        },
        getAssetCalculators: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {

                    var pAssetCalculators = [];
                    var assetCalculators = [];
                    that.portfolio.assets.forEach(
                        function (assetSymbol) {
                            var promise = that.getAssetCalculator(assetSymbol).then(
                                function (calc) {
                                    assetCalculators.push(calc);
                                }
                            );
                            pAssetCalculators.push(promise);
                        }
                    );

                    WinJS.Promise.join(pAssetCalculators).then(
                        function () {
                            complete(assetCalculators);
                        }
                    );
                }
            );
        },
        getAssetCalculator: function (symbol) {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that.requests._assetCalculators) {
                        that.requests._assetCalculators = {};
                    }
                    if (!that.requests._assetCalculators[symbol]) {
                        that.getAsset(symbol).done(
                            function (asset) {
                                var calculator = new AssetCalculator(that, asset, that.simple);
                                that.requests._assetCalculators[symbol] = calculator;
                                complete(that.requests._assetCalculators[symbol]);
                            }
                        );
                    }
                    else complete(that.requests._assetCalculators[symbol]);
                }
            );
        },
        getFilteredAssets: function () {
            var that = this;
            return new WinJS.Promise(
                function (complete) {
                    var assets;
                    that.getAssets().then(
                        function (myAssets) {
                            assets = myAssets;
                            return that.getMinDate();
                        }
                    ).done(
                        function (minDate) {
                            if (!that.requests._filterAssetsRequest) {
                                //var minAsset = null;

                                // select minimum price for each asset
                                //assets.forEach(function (asset) {
                                //    var localMin = asset.prices[0].date;
                                //    if (!minDate) { 
                                //        minDate = localMin;
                                //    }
                                //    else if (localMin.getValue() > minDate.getValue()) {
                                //        minDate = localMin;
                                //        minAsset = asset;
                                //    }
                                //});

                                // finds the start index of each asset and slices the earlier prices from the prices array
                                assets.forEach(function (asset) {
                                    var startIndex = 0;

                                    for (var i = 0; i < asset.prices.length; i++) {
                                        var price = asset.prices[i];
                                        if (price.date.getValue() <= minDate.getValue()) {
                                            startIndex = i;
                                        }
                                        else {
                                            startIndex++;
                                            break;
                                        }
                                    }

                                    asset.prices = asset.prices.slice(startIndex, asset.prices.length);
                                });

                                assets.forEach(function (asset) {
                                    asset.newPrices = new Array(asset.prices.length);
                                    asset.index = 0;
                                });

                                var doneFiltering = false;
                                var numFilteredPrices = 0;

                                while (!doneFiltering) {
                                    var recentDate = assets[0].prices[assets[0].index].date;
                                    assets.forEach(function (asset) {
                                        var price = asset.prices[asset.index];
                                        if (price.date.getValue() >= recentDate.getValue())
                                            recentDate = price.date;
                                    });

                                    var restartTriggered = true;
                                    while (restartTriggered) {
                                        restartTriggered = false;
                                        assets.forEach(function (asset) {
                                            var dateFound = false;
                                            while (!(dateFound || restartTriggered || doneFiltering)) {
                                                var price = asset.prices[asset.index];
                                                if (price.date.getValue() < recentDate.getValue()) {
                                                    if (asset.index < asset.prices.length - 1)
                                                        asset.index++;
                                                    else
                                                        doneFiltering = true;
                                                }
                                                else if (price.date.getValue() == recentDate.getValue())
                                                    dateFound = true;
                                                else {
                                                    recentDate = price.date;
                                                    restartTriggered = true;
                                                }
                                            }
                                        });
                                    }

                                    if (!doneFiltering) {
                                        assets.forEach(function (asset) {
                                            asset.newPrices[numFilteredPrices] = asset.prices[asset.index];
                                            if (asset.index < asset.prices.length - 1)
                                                asset.index++;
                                            else
                                                doneFiltering = true;
                                        });
                                        numFilteredPrices++;
                                    }
                                }

                                assets.forEach(function (asset) {
                                    asset.prices = asset.newPrices.filter(function (price) {
                                        return price != null && price != undefined;
                                    });
                                });

                                that.requests._filterAssetsRequest = assets;
                            }
                            complete(that.requests._filterAssetsRequest);
                        }
                    );
                }
            );
        },
        getAsset: function (symbol) {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    that.getFilteredAssets().done(
                        function (assets) {
                            var asset = _.find(assets,
                                function (a) {
                                    return a.symbol == symbol;
                                }
                            );

                            complete(asset);
                        }
                    );
                }
            );
        },
        getAssets: function () {
            var that = this;

            var assets = [];
            var otherSymbols = ["^IRX", "SPY", "^GSPC"];

            if (!that.requests._getAssetsRequest) {

                that.requests._getAssetsRequest = new WinJS.Promise(
                    function (complete) {
                        if (that.requests._assets) {
                            complete(that.requests._assets);
                        } else {

                            var promises = [];
                            that.portfolio.assets.forEach(function (symbol) {
                                var promise = that._requestResults(symbol);
                                promises.push(promise);
                            });

                            otherSymbols.forEach(function (symbol) {
                                var promise = that._requestResults(symbol);
                                promises.push(promise);
                            });

                            WinJS.Promise.thenEach(promises, function (assetInfo) {
                                assets.push(assetInfo);
                            }).done(function () {
                                that.requests._assets = assets;
                                complete(assets);
                            });
                        }
                    }
                );
            }

            return that.requests._getAssetsRequest;
        },
        getMinDate: function () {
            var that = this;
            
            if (!that.requests._getMinDateRequest) {
                that.requests._getMinDateRequest = new WinJS.Promise(
                    function (complete) {
                        that.getAssets().done(
                            function (assets) {
                                var minDate = null;
                                var minAsset = null;

                                assets.forEach(function (asset) {
                                    var dates = _.pluck(asset.prices, "date");
                                    var localMin = _.min(dates, function (date) { return date.getValue(); });
                                    if (!minDate) {
                                        minDate = localMin;
                                        minAsset = asset;
                                    }
                                    else if (localMin.getValue() > minDate.getValue()) {
                                        minDate = localMin;
                                        minAsset = asset;
                                    }
                                });

                                if (that.portfolio.date.getValue() > minDate.getValue()) minDate = that.portfolio.date;

                                complete(minDate);
                            }
                        );
                    }
                );
            }

            return that.requests._getMinDateRequest;
        },
        setSimple: function(simple) {
            this.simple = simple;
            this.refresh();
        },
        refresh: function () {
            var that = this;
            that.requests = {
                weightedRequests: []
            };
        },
        _verifyWeights: function (weights) {
            var that = this;

            if (weights.id == undefined || !that.requests.weightedRequests[weights.id]) {
                var total = 0;
                that.portfolio.assets.forEach(function (symbol) {
                    if (weights[symbol]) {
                        total += weights[symbol];
                    } else {
                        weights[symbol] = 0;
                    }
                });

                that.portfolio.assets.forEach(function (symbol) { weights[symbol] /= total; });

                weights.id = that.weightedIds++;
                that.requests.weightedRequests[weights.id] = {};
            }
        },
        _filterAssets: function (assets) {
            var that = this;

            
            if (!that.requests._filterAssetsRequest) {
                var minDate = that.getMinDate();
                var minAsset = null;

                // finds the start index of each asset and slices the earlier prices from the prices array
                assets.forEach(function (asset) {
                    var startIndex = 0;

                    for (var i = 0; i < asset.prices.length; i++) {
                        var price = asset.prices[i];
                        if (price.date.getValue() <= minDate.getValue()) {
                            startIndex = i;
                        }
                        else break;
                    }

                    asset.prices = asset.prices.slice(startIndex, asset.prices.length);
                });

                assets.forEach(function (asset) {
                    asset.newPrices = new Array(asset.prices.length);
                    asset.index = 0;
                });

                var doneFiltering = false;
                var numFilteredPrices = 0;

                while (!doneFiltering) {
                    var recentDate = assets[0].prices[assets[0].index].date;
                    assets.forEach(function (asset) {
                        var price = asset.prices[asset.index];
                        if (price.date.getValue() >= recentDate.getValue())
                            recentDate = price.date;
                    });

                    var restartTriggered = true;
                    while (restartTriggered) {
                        restartTriggered = false;
                        assets.forEach(function (asset) {
                            var dateFound = false;
                            while (!(dateFound || restartTriggered || doneFiltering)) {
                                var price = asset.prices[asset.index];
                                if (price.date.getValue() < recentDate.getValue()) {
                                    if (asset.index < asset.prices.length - 1)
                                        asset.index++;
                                    else 
                                        doneFiltering = true;
                                }                                    
                                else if (price.date.getValue() == recentDate.getValue())
                                    dateFound = true;
                                else {
                                    recentDate = price.date;
                                    restartTriggered = true;
                                }
                            }
                        });
                    }

                    if (!doneFiltering) {
                        assets.forEach(function (asset) {
                            asset.newPrices[numFilteredPrices] = asset.prices[asset.index];
                            if (asset.index < asset.prices.length - 1)
                                asset.index++;
                            else 
                                doneFiltering = true;
                        });
                        numFilteredPrices++;
                    }
                }

                assets.forEach(function (asset) {
                    asset.prices = asset.newPrices.filter(function(price) {
                        return price != null && price != undefined;
                    });
                });

                that.requests._filterAssetsRequest = assets;
            }

            return that.requests._filterAssetsRequest;
        },
        _datesAreEqual: function (one, two) {
            return one.getValue() == two.getValue();
        },
        _getUri: function (symbol, startDate, endDate) {
            var uri = "http://ichart.finance.yahoo.com/table.csv";
            uri += "?s=" + symbol;

            if (startDate) {
                uri += "&a=" + startDate.month;
                uri += "&b=" + startDate.date;
                uri += "&c=" + startDate.year;
            }

            if (endDate) {
                uri += "&d=" + endDate.month;
                uri += "&e=" + endDate.date;
                uri += "&f=" + endDate.year;
            }

            uri += "&g=d";

            return uri;
        },
        _getStocksRequest: function (symbol) {
            var that = this;
            if (!that.requests._stocksRequest) that.requests._stocksRequest = {};
            if (!that.requests._stocksRequest[symbol]) {
                var date;
                if (symbol == "^GSPC") {
                    date = new Date(1950, 0, 1);
                } else {
                    date = that.portfolio.date;
                }
                that.requests._stocksRequest[symbol] = WinJS.xhr({
                    type: "GET",
                    url: that._getUri(symbol, that.portfolio.date)
                });
            }

            return that.requests._stocksRequest[symbol];
        },
        _requestResults: function (symbol) {
            var that = this;
            return new WinJS.Promise(
                function (complete, error) {
                    var request = that._getStocksRequest(symbol);
                    request.onerror = function (error) {
                        var i = 0;
                    }
                    request.done(
                            function (request) {

                                if (!that.requests._results) that.requests._results = {};

                                if (!that.requests._results[symbol]) {
                                    var response = request.responseText;

                                    var lines = response.split('\n');

                                    var myStocks = [];

                                    for (var i = lines.length - 1; i > 0; i--) {
                                        var values = lines[i].split(',');

                                        if (values.length == 7) {
                                            var stock = new FolioFixer.DataTypes.AssetPriceItem(values, symbol);
                                            myStocks.push(stock);
                                        }
                                    }

                                    myStocks = _.sortBy(myStocks, function (stock) { return stock.date.getValue(); });

                                    that.requests._results[symbol] = new FolioFixer.DataTypes.AssetInfo(symbol, myStocks);
                                }

                                complete(that.requests._results[symbol]);
                            }
                        );
                }
            );
        }
    }
);

var AssetCalculator = WinJS.Class.define(
    function (portfolioCalculator, asset, simple) {
        this._pCalc = portfolioCalculator;
        this.asset = asset;
        this.simple = simple;
        this._requests = {
            covarianceRequests: {},
            correlationRequests: {}
        };
    },
    {
        getCAPMAnnualMeanReturnRate: function(riskFreeRate, mRP) {
            var that = this;

            return that.getBeta().then(function (beta) {
                return riskFreeRate + beta * mRP;
            });
        },
        getPortfolioCorrelationValues: function() {
            var that = this;

            if (!that._requests._portfolioCorrelationValuesRequest) {
                var assetCalcs;

                that._requests._portfolioCorrelationValuesRequest =
                    that._pCalc.getAssetCalculatorsPlusSPY().then(
                        function (calcs) {
                            assetCalcs = calcs;

                            var correlationRequests = [];

                            assetCalcs.forEach(function (calc) {
                                var request = that._getCorrelation(calc).then(
                                    function (cor) {
                                        return WinJS.Promise.wrap({ symbol: calc.asset.symbol, correlation: cor });
                                    }
                                );

                                correlationRequests.push(request);
                            });

                            return WinJS.Promise.thenEach(correlationRequests);
                        }
                    ).then(
                        function (correlations) {
                            var corReturnObj = {};

                            correlations.forEach(function (cor) {
                                corReturnObj[cor.symbol] = cor.correlation;
                            });

                            return WinJS.Promise.wrap(corReturnObj);
                        }
                    );
            }

            return that._requests._portfolioCorrelationValuesRequest;
        },
        getPortfolioCovarianceValues: function() {
            var that = this;

            if (!that._requests._portfolioCovarianceValuesRequest) {
                that._requests._portfolioCovarianceValuesRequest = new WinJS.Promise(
                    function (complete) {
                        var assetCalcs;

                        that._pCalc.getAssetCalculatorsPlusSPY().then(
                            function (calcs) {
                                assetCalcs = calcs;

                                var covarianceRequests = [];

                                assetCalcs.forEach(function (calc) {
                                    var request = that._getCovariance(calc).then(
                                        function (cov) {
                                            return WinJS.Promise.wrap({ symbol: calc.asset.symbol, covariance: cov });
                                        }
                                    );
                                    covarianceRequests.push(request);
                                });

                                return WinJS.Promise.thenEach(covarianceRequests);
                            }
                        ).done(
                            function (covariances) {
                                var returnObj = {};

                                covariances.forEach(
                                    function (c) {
                                        returnObj[c.symbol] = c.covariance;
                                    }
                                );

                                complete(returnObj);
                            }
                        );
                    }
                );
            }

            return that._requests._portfolioCovarianceValuesRequest;
        },
        getSharpeRatio: function() {
            var that = this;

            if (!that._requests._sharpeRatioRequest) {
                that._requests._sharpeRatioRequest = new WinJS.Promise(
                    function (complete) {
                        var irxCalc;

                        var myAnnualizedRate;
                        var myAnnualizedDeviation;

                        that._pCalc.getIRX().then(
                            function (calc) {
                                irxCalc = calc;
                                return that.getAnnualizedMeanRate();
                            }
                        ).then(
                            function (rate) {
                                myAnnualizedRate = rate;
                                return that.getAnnualizedStandardDeviation();
                            }
                        ).done(
                            function (stdDev) {
                                myAnnualizedDeviation = stdDev;

                                var irxAnnualizedRate = _.reduce(irxCalc.asset.prices, function (memo, rate) {
                                    return memo + rate.adjustedClosePrice;
                                }, 0);

                                irxAnnualizedRate = irxAnnualizedRate / irxCalc.asset.prices.length / 100 / 360 * 252;

                                var sharpeRatio = (myAnnualizedRate - irxAnnualizedRate) / myAnnualizedDeviation;
                                complete(sharpeRatio);
                            }
                        );
                    }
                );
            }

            return that._requests._sharpeRatioRequest;
        },
        getBeta: function () {
            var that = this;

            if (!that._requests._betaRequest) {
                that._requests._betaRequest = new WinJS.Promise(
                    function (complete) {
                        var historicalCorrelation;
                        var annualizedStandardDeviation;
                        var spyAnnualizedStandardDeviation;
                        var irxRates;
                        var irxAvg;

                        that.getHistoricalCorrelation().then(
                            function (correl) {
                                historicalCorrelation = correl;
                                return that.getAnnualizedStandardDeviation();
                            }
                        ).then(
                            function (stdDev) {
                                annualizedStandardDeviation = stdDev;
                                return that._pCalc.getSPY();
                            }
                        ).then(
                            function (spyCalc) {
                                return spyCalc.getAnnualizedStandardDeviation();
                            }
                        ).then(
                            function (stdDev) {
                                spyAnnualizedStandardDeviation = stdDev;
                                return that.getRiskOnlyRates();
                            }
                        ).then(
                            function (rates) {
                                irxRates = rates;
                                return that.getDailyMeanRiskOnlyRate();
                            }
                        ).done(
                            function (avg) {
                                //irxAvg = avg;

                                //var irxStdDev = 0;
                                //irxStdDev = _.reduce(irxRates, function (memo, rate) {
                                //    return memo + Math.pow(rate - irxAvg, 2);
                                //}, 0);

                                //irxStdDev = Math.sqrt(irxStdDev);

                                var beta = historicalCorrelation * (annualizedStandardDeviation) / (spyAnnualizedStandardDeviation);

                                complete(beta);
                            }
                        );
                    }
                );
            }

            return that._requests._betaRequest;
        },
        getHistoricalCorrelation: function() {
            var that = this;

            if (!that._requests._historicalCorrelationRequest) {
                that._requests._historicalCorrelationRequest = new WinJS.Promise(
                    function (complete) {
                        var spyCalc;
                        var spyRates;
                        var spyAvg;
                        var spyStdDev;

                        var myRates;
                        var myAvg;
                        var myStdDev;

                        that._pCalc.getSPY().then(
                            function (calc) {
                                spyCalc = calc;
                                return spyCalc.getRiskOnlyRates();
                            }
                        ).then(
                            function (rates) {
                                spyRates = rates;
                                return spyCalc.getDailyMeanRiskOnlyRate();
                            }
                        ).then(
                            function (mean) {
                                spyAvg = mean;
                                return spyCalc.getDailyStandardDeviation();
                            }
                        ).then(
                            function (dev) {
                                spyStdDev = dev;
                                return that.getRiskOnlyRates();
                            }
                        ).then(
                            function (rates) {
                                myRates = rates;
                                return that.getDailyMeanRiskOnlyRate();
                            }
                        ).then(
                            function (mean) {
                                myAvg = mean;
                                return that.getDailyStandardDeviation();
                            }
                        ).done(
                            function (dev) {
                                myStdDev = dev;

                                var numerator = 0;

                                for (var i = 0; i < spyRates.length; i++) {
                                    numerator += (myRates[i] - myAvg) * (spyRates[i] - spyAvg);
                                }

                                var myDenom = _.reduce(myRates, function (memo, rate) {
                                    return memo + Math.pow(rate - myAvg, 2);
                                }, 0);

                                var spyDenom = _.reduce(spyRates, function (memo, rate) {
                                    return memo + Math.pow(rate - spyAvg, 2);
                                }, 0);

                                var denominator = Math.sqrt(myDenom * spyDenom);

                                var correlation = numerator / denominator;

                                complete(correlation);
                            }
                        );
                    }
                );
            }

            return that._requests._historicalCorrelationRequest;
        },
        getDailyStandardDeviationRiskOnlyRates: function () {
            var that = this;

            if (!that._requests._dailyStandardDeviationRiskOnlyRates) {
                that._requests._dailyStandardDeviationRiskOnlyRates = new WinJS.Promise(
                    function (complete) {
                        that.getDailyVarianceRiskOnlyRates().done(
                            function (variance) {
                                var dailyStandardDeviation = Math.sqrt(variance);
                                complete(dailyStandardDeviation);
                            }
                        );
                    }
                );
            }

            return that._requests._dailyStandardDeviationRiskOnlyRates;
        },
        getDailyVarianceRiskOnlyRates: function () {
            var that = this;

            if (!that._requests._dailyVarianceRiskOnlyRates) {

                that._requests._dailyVarianceRiskOnlyRates = new WinJS.Promise(
                    function (complete) {
                        that.getDailyMeanRiskOnlyRate().done(
                            function (dailyMeanRate) {
                                that.getRiskOnlyRates().done(
                                    function (dailyRatesOfReturn) {
                                        var dailyVariance = _.reduce(dailyRatesOfReturn,
                                            function (memo, rate) {
                                                return memo + Math.pow(rate - dailyMeanRate, 2);
                                            }, 0
                                        );

                                        dailyVariance /= dailyRatesOfReturn.length;

                                        complete(dailyVariance);
                                    }
                                );
                            }
                        );
                    }
                );
            }

            return that._requests._dailyVarianceRiskOnlyRates;
        },
        getDailyMeanRiskOnlyRate: function() {
            var that = this;

            if (!that._requests._dailyMeanRiskOnlyRateRequest) {
                that._requests._dailyMeanRiskOnlyRateRequest = new WinJS.Promise(
                    function (complete) {
                        that.getRiskOnlyRates().done(
                            function (rates) {
                                var avg = _.reduce(rates, function (memo, rate) {
                                    return memo + rate;
                                }, 0);

                                avg /= rates.length;

                                complete(avg);
                            }
                        );
                    }
                );
            }

            return that._requests._dailyMeanRiskOnlyRateRequest;
        },
        getRiskOnlyRates: function () {
            var that = this;

            if (!that._requests._riskOnlyRatesRequest) {
                that._requests._riskOnlyRatesRequest = new WinJS.Promise(
                    function (complete) {
                        var irxCalc;
                        var riskOnlyRates = [];

                        that._pCalc.getIRX().then(
                            function (calc) {
                                irxCalc = calc;
                                return that.getDailyRatesOfReturn();
                            }
                        ).done(
                            function (rates) {
                                var irxLength = irxCalc.asset.prices.length;
                                var ratesLength = rates.length;
                                var irxStartIndex = irxLength - ratesLength;

                                for (var i = 0; i < ratesLength; i++) {
                                    riskOnlyRates.push(rates[i].rate - irxCalc.asset.prices[i + irxStartIndex].adjustedClosePrice / 100 / 365);
                                }

                                complete(riskOnlyRates);
                            }
                        );
                    }
                );
            }

            return that._requests._riskOnlyRatesRequest;
        },
        getAnnualizedStandardDeviationWeight: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._annualizedStandardDeviationWeight) {
                        that._pCalc.getTotalAnnualizedStandardDeviationFactor().done(
                            function (total) {
                                that.getAnnualizedStandardDeviationFactor().done(
                                    function (individual) {
                                        that._annualizedStandardDeviationWeight = individual / total;
                                        complete(that._annualizedStandardDeviationWeight);
                                    }
                                );
                            }
                        );
                    } else complete(that._annualizedStandardDeviationWeight);
                }
            );
        },
        getAnnualizedStandardDeviationFactor: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._annualizedStandardDeviationFactor) {
                        that._pCalc.getNumberOfAssets().done(
                            function (number) {
                                that.getAnnualizedStandardDeviationContribution().done(
                                    function (contr) {
                                        that._annualizedStandardDeviationFactor = 1 / (number * contr);
                                        complete(that._annualizedStandardDeviationFactor);
                                    }
                                );
                            }
                        );
                    } else complete(that._annualizedStandardDeviationFactor);
                }
            );
        },
        getAnnualizedStandardDeviationContribution: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._annualizedStandardDeviationContribution) {
                        that._pCalc.getTotalAnnualizedStandardDeviation().done(
                            function (total) {
                                that.getAnnualizedStandardDeviation().done(
                                    function (dev) {
                                        that._annualizedStandardDeviationContribution = dev / total;
                                        complete(that._annualizedStandardDeviationContribution);
                                    }
                                );
                            }
                        );
                    }
                    else complete(that._annualizedStandardDeviationContribution);
                }
            );
        },
        getAnnualizedStandardDeviation: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._annualizedStandardDeviation) {
                        that.getAnnualizedVariance().done(
                            function (variance) {
                                that._annualizedStandardDeviation = Math.sqrt(variance);
                                complete(that._annualizedStandardDeviation);
                            }
                        );
                    }
                    else complete(that._annualizedStandardDeviation);
                }
            );
        },
        getDailyStandardDeviation: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._dailyStandardDeviation) {
                        that.getDailyVariance().done(
                            function (variance) {
                                that._dailyStandardDeviation = Math.sqrt(variance);
                                complete(that._dailyStandardDeviation);
                            }
                        );
                    }
                    else complete(that._dailyStandardDeviation);
                }
            );
        },
        getAnnualizedVariance: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._annualizedVariance) {
                        that.getDailyVariance().done(
                            function (dailyVariance) {
                                that._annualizedVariance = dailyVariance * 252;
                                complete(that._annualizedVariance);
                            }
                        );
                    }
                    else complete(that._annualizedVariance);
                }
            );
        },
        getDailyVariance: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._dailyVariance) {
                        that.getDailyMeanRate().done(
                            function (dailyMeanRate) {
                                that.getDailyRatesOfReturn().done(
                                    function (dailyRatesOfReturn) {
                                        var dailyVariance = _.reduce(dailyRatesOfReturn,
                                            function (memo, rate) {
                                                return memo + Math.pow(rate.rate - dailyMeanRate, 2);
                                            }, 0
                                        );

                                        dailyVariance /= dailyRatesOfReturn.length;

                                        that._dailyVariance = dailyVariance;
                                        complete(that._dailyVariance);
                                    }
                                );
                            }
                        );
                    }
                    else complete(that._dailyVariance);
                }
            );
        },
        getAnnualizedMeanRate: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._annualizedMeanRate) {
                        that.getDailyMeanRate().done(
                            function (rate) {
                                that._annualizedMeanRate = rate * 252;
                                complete(that._annualizedMeanRate);
                            }
                        );
                    }
                    else complete(that._annualizedMeanRate);
                }
            );
        },
        getDailyMeanRate: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._dailyMeanRate) {
                        that.getDailyRatesOfReturn().done(
                            function (rates) {
                                var dailyMeanRate = _.reduce(rates,
                                    function (memo, rate) {
                                        return memo + rate.rate;
                                    }, 0);
                                dailyMeanRate /= rates.length;

                                that._dailyMeanRate = dailyMeanRate;
                                complete(that._dailyMeanRate);
                            }
                        );
                    }
                    else complete(that._dailyMeanRate);
                }
            );
        },
        getDailyRatesOfReturn: function () {
            var that = this;

            return new WinJS.Promise(
                function (complete) {
                    if (!that._dailyRatesOfReturn) {
                        var ratesOfReturn = [];
                        for (var i = 0; i < that.asset.prices.length - 1; i++) {
                            ratesOfReturn.push(new FolioFixer.DataTypes.DailyRateOfReturnInfo(that.asset.prices[i], that.asset.prices[i + 1],that.simple));
                        }
                        that._dailyRatesOfReturn = ratesOfReturn;
                        complete(that._dailyRatesOfReturn);
                    }
                    else complete(that._dailyRatesOfReturn);
                }
            );
        },
        _getCorrelation: function(assetCalc) {
            var that = this;
            
            if (!that._requests.correlationRequests[assetCalc.asset.symbol]) {
                var thisStdDev;
                var thatStdDev;
                var cov;

                that._requests.correlationRequests[assetCalc.asset.symbol] = assetCalc._requests.correlationRequests[that.asset.symbol] ||
                    that._getCovariance(assetCalc).then(
                        function (covariance) {
                            cov = covariance;
                            return that.getDailyStandardDeviationRiskOnlyRates();
                        }
                    ).then(
                        function (stdDev) {
                            thisStdDev = stdDev;
                            return assetCalc.getDailyStandardDeviationRiskOnlyRates();
                        }
                    ).then(
                        function (stdDev) {
                            thatStdDev = stdDev;

                            return WinJS.Promise.wrap(cov / (thisStdDev * thatStdDev));
                        }
                    );
            }

            return that._requests.correlationRequests[assetCalc.asset.symbol];
        },
        _getCovariance: function (assetCalc) {
            var that = this;

            if (!that._requests.covarianceRequests[assetCalc.asset.symbol]) {
                if (assetCalc._requests.covarianceRequests[that.asset.symbol]) {
                    that._requests.covarianceRequests[assetCalc.asset.symbol] = assetCalc._requests.covarianceRequests[that.asset.symbol];
                }
                else {
                    that._requests.covarianceRequests[assetCalc.asset.symbol] = new WinJS.Promise(
                        function (complete) {
                            var theseRiskyRates;
                            var thisMeanRiskyRate;
                            var thoseRiskyRates;
                            var thatMeanRiskyRate;

                            that.getRiskOnlyRates().then(
                                function (rates) {
                                    theseRiskyRates = rates;
                                    return that.getDailyMeanRiskOnlyRate();
                                }
                            ).then(
                                function (mean) {
                                    thisMeanRiskyRate = mean;
                                    return assetCalc.getRiskOnlyRates();
                                }
                            ).then(
                                function (rates) {
                                    thoseRiskyRates = rates;
                                    return assetCalc.getDailyMeanRiskOnlyRate();
                                }
                            ).done(
                                function (mean) {
                                    thatMeanRiskyRate = mean;

                                    var covariance = 0;
                                    for (var i = 0; i < theseRiskyRates.length; i++) {
                                        covariance += (theseRiskyRates[i] - thisMeanRiskyRate)*(thoseRiskyRates[i] - thatMeanRiskyRate);
                                        //covariance += (theseRiskyRates[i] * thoseRiskyRates[i]);
                                    }
                                    covariance /= theseRiskyRates.length;
                                    //covariance -= thisMeanRiskyRate * thatMeanRiskyRate;

                                    complete(covariance);
                                }
                            );
                        }
                    );
                }
            }

            return that._requests.covarianceRequests[assetCalc.asset.symbol];
        }
    }
);

WinJS.Namespace.define("FolioFixer.Calculators", {
    PortfolioCalculator: PortfolioCalculator,
    AssetCalculator: AssetCalculator
});